﻿using ArtMath.Attribute;
using ArtMath.Component.Point;
using ArtMath.Converter;
using ArtMath.Core.Data;
using ArtMath.Core.Helper;
using ArtMath.Data;
using ArtMath.Helper;
using ArtMath.PointCollection;
using ArtMath.Resource;
using ArtMath.Setting;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using ArtMath.CommonType;

namespace ArtMath.Component.ConicalSection
{
    /// <summary>
    /// 抛物线基类
    /// </summary>
    [Serializable]
    public abstract class ParabolaBase : LineBase, IConic
    {
        double step;
        //protected double focusAngle;
        protected Parabola ske = new Parabola() { Curves = new LengthCurve[] { new LengthCurve() } },
            pske = new Parabola() { Curves = new Curve[] { new Curve() } };
        protected LengthCurve newCur = new LengthCurve();
        protected Curve newPCur = new Curve();
        //protected PointD center, Pcenter;
        List<PointF[]> track = new List<PointF[]>();
        PadStringX prec;
        PropertyDisplayer pDisplayer, fpDisplayer, ctDisplayer, eDisplayer;
        public ParabolaBase(SketchPad pad, string name, ToolTypeName toolType, bool addToList = true) : base(pad, name, toolType, addToList)
        {
            Accuracy = new PadStringX(Pad, Settings.Instance.ParabolaAccuracy);
            step = 1 / Settings.Instance.ParabolaAccuracy;
            FocusDirectrixDistanceDisplayer = new PropertyDisplayer(this, "FocusDirectrixDistance");
            FocusPointDisplayer = new PropertyDisplayer(this, "Focus") { OffSet = AnchorOffset };
            CenterDisplayer = new PropertyDisplayer(this, "Center");
            EccentricityDisplayer = new PropertyDisplayer(this, "Eccentricity");
        }
        public override PointD MassPoint
        {
            get { return Center; }
        }
        public override PointCollectionsManager Skeleton
        {
            get
            {
                return new PointCollectionsManager(ske);
            }
        }
        public override PointCollectionsManager PSkeleton
        {
            get
            {
                return new PointCollectionsManager(pske);
            }
        }
        public override PointCollectionsManager NewSkeleton
        {
            get
            {
                build(newCur, null, NewFocusDirectrixDistance, NewFocusAngle, NewCenter);
                return new PointCollectionsManager(new Parabola() { Center = NewCenter, RotateAngle = NewFocusAngle, FocusDirectrixDistance = NewFocusDirectrixDistance, Curves = new LengthCurve[] { newCur } });
            }
        }
        public override PointCollectionsManager NewPSkeleton
        {
            get
            {
                build(null, newPCur, NewFocusDirectrixDistance, NewFocusAngle, NewCenter);
                return new PointCollectionsManager(new Parabola() { Center = NewPCenter, Curves = new Curve[] { newPCur } });
            }
        }
        [MultilingualCategory("Parameter"), MultilingualDisplayName("FocusDirectrixDistance"), ReadOnly(true)]
        public double FocusDirectrixDistance
        {
            get { return ske.FocusDirectrixDistance; }
            protected set
            {
                ske.FocusDirectrixDistance = value;
                Pad.Canvas.Invalidate();
            }
        }
        [Browsable(false)]
        public virtual double NewFocusDirectrixDistance
        {
            get { return FocusDirectrixDistance; }
        }
        [MultilingualCategory("Parameter"), MultilingualDisplayName("Focus"), TypeConverter(typeof(IntelliPointConverter))]
        public virtual PointD Focus
        {
            get { return Center.Offset(FocusAngle, FocusDirectrixDistance / 2); }
        }
        [Browsable(false)]
        public virtual PointD NewFocus
        {
            get { return NewCenter.Offset(NewFocusAngle, NewFocusDirectrixDistance / 2); }
        }
        [Browsable(false)]
        public virtual PointD PFocus
        {
            get { return Pad.XYToPxy(Focus); }
        }
        [Browsable(false)]
        public virtual PointD NewPFocus
        {
            get { return Pad.XYToPxy(NewFocus); }
        }
        [MultilingualCategory("Structure"), DefaultValue(typeof(PadStringX), "1000"), MultilingualDisplayName("Accuracy")]
        public PadStringX Accuracy
        {
            get { return prec; }
            set
            {
                if (prec is object)
                {
                    prec.ParametersValueChanged -= Accuracy_ParametersValueChanged;
                    prec.ExpressionSet -= Accuracy_ExpressionSet;
                }
                prec = value;
                if (prec is object)
                {
                    prec.ParametersValueChanged += Accuracy_ParametersValueChanged;
                    prec.ExpressionSet += Accuracy_ExpressionSet;
                }
            }
        }
        [MultilingualCategory("Parameter"), MultilingualDisplayName("DirectedAngle"), TypeConverter(typeof(IntelliAngleConverter)), ReadOnly(true)]
        public double FocusAngle
        {
            get { return ske.RotateAngle; }
            protected set
            {
                ske.RotateAngle = value;
                Pad.Canvas.Invalidate();
            }
        }
        [Browsable(false)]
        public virtual double NewFocusAngle
        {
            get { return FocusAngle; }
        }
        [MultilingualCategory(null, "Sketchpad", "Display"), MultilingualDisplayName(null, "Eccentricity", "Displayer")]
        public PropertyDisplayer EccentricityDisplayer
        {
            get { return eDisplayer; }
            set { eDisplayer = value; }
        }
        //[Browsable(false)]
        //public PaintableCurve[] Curves
        //{
        //    get { return new PaintableCurve[] { cur }; }
        //}
        //[Browsable(false)]
        //public PaintableCurve[] NewCurves
        //{
        //    get
        //    {
        //        build(newCur, NewFocusDirectrixDistance, NewFocusAngle, NewCenter);
        //        //newCur.Samples.Clear();
        //        //for (double theta = 0; theta < Math.PI / 2; theta += step)
        //        //{
        //        //    if (theta <= 0) continue;
        //        //    double tan = Math.Tan(theta);
        //        //    PointD pt = new PointD(2 * p / tan / tan, 2 * p / tan).Rotate(NewFocusAngle) + NewCenter;
        //        //    if (Pad.Coordinate != null) pt = Pad.Coordinate.XYToPxy(pt);
        //        //    if (pt.IsPaintable) newCur.Samples.Add(pt);
        //        //}
        //        //for (double theta = 1.5 * Math.PI; theta < Core.Helper.Angle.RadRound; theta += step)
        //        //{
        //        //    double tan = Math.Tan(theta);
        //        //    PointD pt = new PointD(2 * p / tan / tan, 2 * p / tan).Rotate(NewFocusAngle) + NewCenter;
        //        //    if (Pad.Coordinate != null) pt = Pad.Coordinate.XYToPxy(pt);
        //        //    if (pt.IsPaintable) newCur.Samples.Add(pt);
        //        //}
        //        return new PaintableCurve[] { newCur };
        //    }
        //}
        [MultilingualCategory("Parameter"), MultilingualDisplayName(null, "Parabola", "Center"), TypeConverter(typeof(IntelliPointConverter))]
        public PointD Center
        {
            get { return ske.Center; }
        }
        [Browsable(false)]
        public PointD PCenter
        {
            get { return pske.Center; }
        }
        [Browsable(false)]
        public virtual PointD NewCenter
        {
            get { return Center; }
        }
        [Browsable(false)]
        public virtual PointD NewPCenter
        {
            get { return PCenter; }
        }
        [MultilingualCategory(null, "Sketchpad", "Display"), MultilingualDisplayName(null, "FocusDirectrixDistance", "Displayer")]
        public PropertyDisplayer FocusDirectrixDistanceDisplayer
        {
            get { return pDisplayer; }
            set { pDisplayer = value; }
        }
        [MultilingualCategory(null, "Sketchpad", "Display"), MultilingualDisplayName(null, "Focus", "Displayer")]
        public PropertyDisplayer FocusPointDisplayer
        {
            get { return fpDisplayer; }
            set { fpDisplayer = value; }
        }
        [MultilingualCategory(null, "Sketchpad", "Display"), MultilingualDisplayName(null, "Parabola", "Center", "Displayer")]
        public PropertyDisplayer CenterDisplayer
        {
            get { return ctDisplayer; }
            set { ctDisplayer = value; }
        }
        [MultilingualCategory("Parameter"), MultilingualDisplayName("Eccentricity")]
        public double Eccentricity
        {
            get { return 1; }
        }
        [Browsable(false)]
        public double NewEccentricity
        {
            get { return 1; }
        }
        void Accuracy_ParametersValueChanged(DocStringX sender, ParametersValueChangedEventArgs e)
        {
            if (prec.Value > 0)
            {
                step = 1 / prec.Value;
                BuildSamples();
            }
        }
        void Accuracy_ExpressionSet(StringX sender, ExpressionSetEventArgs e)
        {
            if (prec.Value > 0)
            {
                step = 1 / prec.Value;
                BuildSamples();
            }
            else
            {
                Pad.ShowMessage(Resources.JoinLocalizedString(string.Format(Resources.Translate("Of"), Resources.Translate("Parabola"), Resources.Translate("Accuracy")), Resources.Translate("MustPositive")), MessageType.Error);
                prec.SetExpression(e.OldExpression, false);
            }
        }
        public override PointD AnchorPoint(PropertyDisplayer disp)
        {
            if (disp == FocusPointDisplayer)
            {
                return PFocus;
            }
            return PCenter;
        }
        public override void CheckInRegion(RectangleD rect)
        {
            SetInPicker(rect.IntersectCurves(pske.Curves));
            base.CheckInRegion(rect);
        }
        public override void ComponentBuilding(Base component)
        {
            BuildSamples();
            ShowStatus($"{Resources.Translate("Creating")}{Resources.ColonSeparate}{Name}{Resources.LeftParenthesis}{Resources.Translate("FocusDirectrixDistance")} = {FocusDirectrixDistance}）");
            base.ComponentBuilding(component);
        }
        public override void Paint(Graphics gra)
        {
            if (ObjectState == ObjectState.Normal && Visible && IsCreationCompleted)
            {
                pske.Curves[0].Paint(gra, LinePen);
            }
            base.Paint(gra);
        }
        public override void PaintTrack(Graphics gra)
        {
            if (ShowTrack_)
            {
                for (int i = 0; i < track.Count; i++)
                {
                    gra.DrawCurve(TrackPen, track[i]);
                }
            }
            base.PaintTrack(gra);
        }
        public override void ComponentChanged(ShapeBase component)
        {
            FocusDirectrixDistance = NewFocusDirectrixDistance;
            ske.Center = NewCenter;
            pske.Center = NewPCenter;
            FocusAngle = NewFocusAngle;
            BuildSamples();
            base.ComponentChanged(component);
        }
        //public override PointD SlideDestination(PointD currentLocation, double length, out bool reverse)
        //{
        //    return ske.SlideDestination(currentLocation, length, out reverse);//使用NewSkeleton可能会降低性能
        //}
        public override void RefreshPropertyDisplayersText()
        {
            FocusDirectrixDistanceDisplayer.RefreshText();
            FocusPointDisplayer.RefreshText();
            CenterDisplayer.RefreshText();
            base.RefreshPropertyDisplayersText();
        }
        public override void RefreshPropertyDisplayersLocation()
        {
            FocusDirectrixDistanceDisplayer.RefreshLocation();
            FocusPointDisplayer.RefreshLocation();
            CenterDisplayer.RefreshLocation();
            base.RefreshPropertyDisplayersLocation();
        }
        public override void PaintProperties(Graphics gra)
        {
            if (ObjectState == ObjectState.Normal && Visible)
            {
                FocusDirectrixDistanceDisplayer.Paint(gra);
                FocusPointDisplayer.Paint(gra);
                CenterDisplayer.Paint(gra);
            }
            base.PaintProperties(gra);
        }
        public override void InternalPrepare(PointBase movePoint, ShapeBase sender = null)
        {
            foreach (PointBase item in OutChildren_Point)
            {
                PointD std = (item.ToPointD() - Center).Rotate(-FocusAngle);
                //double angle = (Math.Atan(Math.Sqrt(2 * FocusDirectrixDistance / std.X)) + Math.Atan(2 * FocusDirectrixDistance / std.Y)) / 2;
                //angle = Math.PI / 4;
                ControlledOutPointsOriginalInfo.Add(item, new double[] { 2 * FocusDirectrixDistance / std.Y });
            }
            base.InternalPrepare(movePoint, sender);
        }
        public override PointD GetOutFreePointRecommendNewLocation(PointBase pt, bool getXY = true)
        {
            double y = 2 * NewFocusDirectrixDistance / ControlledOutPointsOriginalInfo[pt][0];
            PointD xy = NewCenter + new PointD(y / ControlledOutPointsOriginalInfo[pt][0], y).Rotate(NewFocusAngle);
            return getXY ? xy : Pad.XYToPxy(xy);
        }
        public override void InternalClean(PointBase movedPoint, ShapeBase sender = null)
        {
            ControlledOutPointsOriginalInfo.Clear();
            base.InternalClean(movedPoint, sender);
        }
        //protected override PointD GetAbsorbedPoint(PointD PLocation, bool getNew = false)
        //{
        //    return PLocation.GetNearestPointFromCurves(getNew ? NewCurves : Curves);
        //}
        public override void PaintVirtualObjects(Graphics gra)
        {
            if (CanDrawVirtual)
            {
                Pen p = Core.Helper.Painter.VirtualLinePen;
                p.DashStyle = DashStyle.Solid;
                pske.Curves[0].Paint(gra, p);
            }
            base.PaintVirtualObjects(gra);
        }
        void build(Curve cur, Curve pCur, double p, double focusAngle, PointD center)
        {
            if (cur == null && pCur == null) return;
            cur?.Clear();
            pCur?.Clear();
            //cur.TangentVectors.Clear();
            for (double theta = 0; theta < Core.Helper.Angle.RightAngle; theta += step)
            {
                if (theta == 0) continue;
                //添加采样点
                double tan = Math.Tan(theta), tmp = 2 * p / tan;
                PointD pt = new PointD(tmp / tan, tmp).Rotate(focusAngle) + center;
                cur?.Add(pt);
                if (pCur != null)
                {
                    if (Pad.Coordinate != null) pt = Pad.Coordinate.XYToPxy(pt);
                    if (pt.IsPaintable) pCur.Add(pt);
                }
                //添加该点处切线方向向量
                //tan = Math.Tan(theta - Curve.DeltaX);
                //tmp = 2 * p / tan;
                //pt = new PointD(tmp / tan, tmp).Rotate(focusAngle);
                //tan = Math.Tan(theta + Curve.DeltaX);
                //tmp = 2 * p / tan;
                //PointD pt2 = new PointD(tmp / tan, tmp).Rotate(focusAngle);
                //if (Pad.Coordinate != null)
                //{
                //    pt = Pad.Coordinate.XYToPxy(pt);
                //    pt2 = Pad.Coordinate.XYToPxy(pt2);
                //}
                //cur.TangentVectors.Add(pt2 - pt);
            }
            //pt = center;
            //if (Pad.Coordinate != null) pt = Pad.Coordinate.XYToPxy(pt);
            //if (pt.IsPaintable) cur.Samples.Add(pt);
            for (double theta = 1.5 * Math.PI; theta < Core.Helper.Angle.RadRound; theta += step)
            {
                //if (theta == 1.5 * Math.PI) continue;
                //添加采样点
                double tan = Math.Tan(theta), tmp = 2 * p / tan;
                PointD pt = new PointD(tmp / tan, tmp).Rotate(focusAngle) + center;
                cur?.Add(pt);
                if (pCur != null)
                {
                    if (Pad.Coordinate != null) pt = Pad.Coordinate.XYToPxy(pt);
                    if (pt.IsPaintable) pCur.Add(pt);
                }
                //添加该点处切线方向向量
                //tan = Math.Tan(theta - Curve.DeltaX);
                //tmp = 2 * p / tan;
                //pt = new PointD(tmp / tan, tmp).Rotate(focusAngle);
                //tan = Math.Tan(theta + Curve.DeltaX);
                //tmp = 2 * p / tan;
                //PointD pt2 = new PointD(tmp / tan, tmp).Rotate(focusAngle);
                //if (Pad.Coordinate != null)
                //{
                //    pt = Pad.Coordinate.XYToPxy(pt);
                //    pt2 = Pad.Coordinate.XYToPxy(pt2);
                //}
                //cur.TangentVectors.Add(pt2 - pt);
            }
        }
        protected override string getFormula()
        {
            if (Pad.CoordinateType == CoordinateSystemType.PolarCoordinate)
            {
                return string.Empty;//不会求极坐标方程，暂时留空
            }
            else
            {
                return $"({Math.Cos(FocusAngle)}(y-{Center.Y})-{Math.Sin(FocusAngle)}(x-{Center.X}))^2={2 * FocusDirectrixDistance}({Math.Sin(FocusAngle)}(y-{Center.Y})+{Math.Cos(FocusAngle)}(x-{Center.X}))";
            }
        }
        public void BuildSamples()
        {
            build(ske.Curves[0], pske.Curves[0], FocusDirectrixDistance, FocusAngle, Center);
            //cur.Samples.Clear();
            //cur.Samples.AddRange(NewCurves[0].Samples);
            //for (double theta = 0; theta < Math.PI / 2; theta += step)
            //{
            //    if (theta <= 0) continue;
            //    double tan = Math.Tan(theta);
            //    PointD pt = new PointD(2 * p / tan / tan, 2 * p / tan).Rotate(RotateAngle) + Center;
            //    if (Pad.Coordinate != null) pt = Pad.Coordinate.XYToPxy(pt);
            //    if (pt.IsPaintable) cur.Samples.Add(pt);
            //}
            //for (double theta = 1.5 * Math.PI; theta < Core.Helper.Angle.RadRound; theta += step)
            //{
            //    double tan = Math.Tan(theta);
            //    PointD pt = new PointD(2 * p / tan / tan, 2 * p / tan).Rotate(RotateAngle) + Center;
            //    if (Pad.Coordinate != null) pt = Pad.Coordinate.XYToPxy(pt);
            //    if (pt.IsPaintable) cur.Samples.Add(pt);
            //}
        }
        //public override bool OnObject(PointD pt)
        //{
        //    PointD tmp = Focus;
        //    double[] abc = (2 * Center - tmp).GetABC(-1 / Center.GetSlope(tmp));
        //    return pt.DistanceTo(Focus).AlmostEqual(pt.DistanceTo(abc[0], abc[1], abc[2]));
        //}
        public override PropertyDisplayer[] PropertyDisplayersCheckNearBy(PointD Plocation)
        {
            List<PropertyDisplayer> res = new List<PropertyDisplayer>(base.PropertyDisplayersCheckNearBy(Plocation));
            if (FocusDirectrixDistanceDisplayer.InRegion(Plocation))
            {
                res.Add(FocusDirectrixDistanceDisplayer);
            }
            if (FocusPointDisplayer.InRegion(Plocation))
            {
                res.Add(FocusPointDisplayer);
            }
            if (CenterDisplayer.InRegion(Plocation))
            {
                res.Add(CenterDisplayer);
            }
            return res.ToArray();
        }
        public PointD[] GetTangentPoints(PointD passPoint)
        {
            return CurveTangent.GetParabolaTangentPoints(NewCenter, NewFocusDirectrixDistance, NewFocusAngle, passPoint);
        }

        public double? GetTangentLineSlope(PointD pt)
        {
            //if (!OnLine(pt))
            //{
            //    if (Pad.Coordinate != null) pt = Pad.Coordinate.XYToPxy(pt);
            //    pt = pt.GetNearestPointFromCurves(NewCurves);
            //    if (Pad.Coordinate != null) pt = Pad.Coordinate.PxyToXY(pt);
            //}
            PointD f = NewFocus;
            return Math.Tan((pt.GetAngle(f) + f.GetAngle(2 * NewCenter - f)) / 2);
        }
    }
}
